// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/child/fileapi/webfilewriter_base.h"

#include <stdint.h>

#include <memory>

#include "base/logging.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebFileError.h"
#include "third_party/WebKit/public/platform/WebFileWriterClient.h"
#include "third_party/WebKit/public/platform/WebURL.h"
#include "url/gurl.h"

namespace content {

namespace {

    // We use particular offsets to trigger particular behaviors
    // in the TestableFileWriter.
    const int kNoOffset = -1;
    const int kBasicFileTruncate_Offset = 1;
    const int kErrorFileTruncate_Offset = 2;
    const int kCancelFileTruncate_Offset = 3;
    const int kCancelFailedTruncate_Offset = 4;
    const int kBasicFileWrite_Offset = 1;
    const int kErrorFileWrite_Offset = 2;
    const int kMultiFileWrite_Offset = 3;
    const int kCancelFileWriteBeforeCompletion_Offset = 4;
    const int kCancelFileWriteAfterCompletion_Offset = 5;

    GURL mock_path_as_gurl()
    {
        return GURL("MockPath");
    }

} // namespace

class TestableFileWriter : public WebFileWriterBase {
public:
    explicit TestableFileWriter(blink::WebFileWriterClient* client)
        : WebFileWriterBase(mock_path_as_gurl(), client)
    {
        reset();
    }

    void reset()
    {
        received_truncate_ = false;
        received_truncate_path_ = GURL();
        received_truncate_offset_ = kNoOffset;
        received_write_ = false;
        received_write_path_ = GURL();
        received_write_offset_ = kNoOffset;
        received_write_blob_uuid_ = std::string();
        received_cancel_ = false;
    }

    bool received_truncate_;
    GURL received_truncate_path_;
    int64_t received_truncate_offset_;
    bool received_write_;
    GURL received_write_path_;
    std::string received_write_blob_uuid_;
    int64_t received_write_offset_;
    bool received_cancel_;

protected:
    void DoTruncate(const GURL& path, int64_t offset) override
    {
        received_truncate_ = true;
        received_truncate_path_ = path;
        received_truncate_offset_ = offset;

        if (offset == kBasicFileTruncate_Offset) {
            DidSucceed();
        } else if (offset == kErrorFileTruncate_Offset) {
            DidFail(base::File::FILE_ERROR_NOT_FOUND);
        } else if (offset == kCancelFileTruncate_Offset) {
            cancel();
            DidSucceed(); // truncate completion
            DidSucceed(); // cancel completion
        } else if (offset == kCancelFailedTruncate_Offset) {
            cancel();
            DidFail(base::File::FILE_ERROR_NOT_FOUND); // truncate completion
            DidSucceed(); // cancel completion
        } else {
            FAIL();
        }
    }

    void DoWrite(const GURL& path,
        const std::string& blob_uuid,
        int64_t offset) override
    {
        received_write_ = true;
        received_write_path_ = path;
        received_write_offset_ = offset;
        received_write_blob_uuid_ = blob_uuid;

        if (offset == kBasicFileWrite_Offset) {
            DidWrite(1, true);
        } else if (offset == kErrorFileWrite_Offset) {
            DidFail(base::File::FILE_ERROR_NOT_FOUND);
        } else if (offset == kMultiFileWrite_Offset) {
            DidWrite(1, false);
            DidWrite(1, false);
            DidWrite(1, true);
        } else if (offset == kCancelFileWriteBeforeCompletion_Offset) {
            DidWrite(1, false);
            cancel();
            DidWrite(1, false);
            DidWrite(1, false);
            DidFail(base::File::FILE_ERROR_FAILED); // write completion
            DidSucceed(); // cancel completion
        } else if (offset == kCancelFileWriteAfterCompletion_Offset) {
            DidWrite(1, false);
            cancel();
            DidWrite(1, false);
            DidWrite(1, false);
            DidWrite(1, true); // write completion
            DidFail(base::File::FILE_ERROR_FAILED); // cancel completion
        } else {
            FAIL();
        }
    }

    void DoCancel() override { received_cancel_ = true; }
};

class FileWriterTest : public testing::Test,
                       public blink::WebFileWriterClient {
public:
    FileWriterTest()
    {
        reset();
    }

    blink::WebFileWriter* writer()
    {
        return testable_writer_.get();
    }

    // WebFileWriterClient overrides
    void didWrite(long long bytes, bool complete) override
    {
        EXPECT_FALSE(received_did_write_complete_);
        ++received_did_write_count_;
        received_did_write_bytes_total_ += bytes;
        if (complete)
            received_did_write_complete_ = true;

        if (delete_in_client_callback_)
            testable_writer_.reset(NULL);
    }

    void didTruncate() override
    {
        EXPECT_FALSE(received_did_truncate_);
        received_did_truncate_ = true;
        if (delete_in_client_callback_)
            testable_writer_.reset(NULL);
    }

    void didFail(blink::WebFileError error) override
    {
        EXPECT_FALSE(received_did_fail_);
        received_did_fail_ = true;
        fail_error_received_ = error;
        if (delete_in_client_callback_)
            testable_writer_.reset(NULL);
    }

protected:
    void reset()
    {
        testable_writer_.reset(new TestableFileWriter(this));
        delete_in_client_callback_ = false;
        received_did_write_count_ = 0;
        received_did_write_bytes_total_ = 0;
        received_did_write_complete_ = false;
        received_did_truncate_ = false;
        received_did_fail_ = false;
        fail_error_received_ = static_cast<blink::WebFileError>(0);
    }

    std::unique_ptr<TestableFileWriter> testable_writer_;
    bool delete_in_client_callback_;

    // Observed WebFileWriterClient artifacts.
    int received_did_write_count_;
    long long received_did_write_bytes_total_;
    bool received_did_write_complete_;
    bool received_did_truncate_;
    bool received_did_fail_;
    blink::WebFileError fail_error_received_;

    DISALLOW_COPY_AND_ASSIGN(FileWriterTest);
};

TEST_F(FileWriterTest, BasicFileWrite)
{
    // Call the webkit facing api.
    const std::string kBlobId("1234");
    writer()->write(kBasicFileWrite_Offset,
        blink::WebString::fromUTF8(kBlobId));

    // Check that the derived class gets called correctly.
    EXPECT_TRUE(testable_writer_->received_write_);
    EXPECT_EQ(testable_writer_->received_write_path_,
        mock_path_as_gurl());
    EXPECT_EQ(kBasicFileWrite_Offset,
        testable_writer_->received_write_offset_);
    EXPECT_EQ(kBlobId, testable_writer_->received_write_blob_uuid_);
    EXPECT_FALSE(testable_writer_->received_truncate_);
    EXPECT_FALSE(testable_writer_->received_cancel_);

    // Check that the client gets called correctly.
    EXPECT_EQ(1, received_did_write_count_);
    EXPECT_TRUE(received_did_write_complete_);
    EXPECT_EQ(1, received_did_write_bytes_total_);
    EXPECT_FALSE(received_did_truncate_);
    EXPECT_FALSE(received_did_fail_);
}

TEST_F(FileWriterTest, BasicFileTruncate)
{
    // Call the webkit facing api.
    writer()->truncate(kBasicFileTruncate_Offset);

    // Check that the derived class gets called correctly.
    EXPECT_TRUE(testable_writer_->received_truncate_);
    EXPECT_EQ(mock_path_as_gurl(),
        testable_writer_->received_truncate_path_);
    EXPECT_EQ(kBasicFileTruncate_Offset,
        testable_writer_->received_truncate_offset_);
    EXPECT_FALSE(testable_writer_->received_write_);
    EXPECT_FALSE(testable_writer_->received_cancel_);

    // Check that the client gets called correctly.
    EXPECT_TRUE(received_did_truncate_);
    EXPECT_EQ(0, received_did_write_count_);
    EXPECT_FALSE(received_did_fail_);
}

TEST_F(FileWriterTest, ErrorFileWrite)
{
    // Call the webkit facing api.
    const std::string kBlobId("1234");
    writer()->write(kErrorFileWrite_Offset,
        blink::WebString::fromUTF8(kBlobId));

    // Check that the derived class gets called correctly.
    EXPECT_TRUE(testable_writer_->received_write_);
    EXPECT_EQ(testable_writer_->received_write_path_,
        mock_path_as_gurl());
    EXPECT_EQ(kErrorFileWrite_Offset,
        testable_writer_->received_write_offset_);
    EXPECT_EQ(kBlobId, testable_writer_->received_write_blob_uuid_);
    EXPECT_FALSE(testable_writer_->received_truncate_);
    EXPECT_FALSE(testable_writer_->received_cancel_);

    // Check that the client gets called correctly.
    EXPECT_TRUE(received_did_fail_);
    EXPECT_EQ(blink::WebFileErrorNotFound, fail_error_received_);
    EXPECT_EQ(0, received_did_write_count_);
    EXPECT_FALSE(received_did_truncate_);
}

TEST_F(FileWriterTest, ErrorFileTruncate)
{
    // Call the webkit facing api.
    writer()->truncate(kErrorFileTruncate_Offset);

    // Check that the derived class gets called correctly.
    EXPECT_TRUE(testable_writer_->received_truncate_);
    EXPECT_EQ(mock_path_as_gurl(),
        testable_writer_->received_truncate_path_);
    EXPECT_EQ(kErrorFileTruncate_Offset,
        testable_writer_->received_truncate_offset_);
    EXPECT_FALSE(testable_writer_->received_write_);
    EXPECT_FALSE(testable_writer_->received_cancel_);

    // Check that the client gets called correctly.
    EXPECT_TRUE(received_did_fail_);
    EXPECT_EQ(blink::WebFileErrorNotFound, fail_error_received_);
    EXPECT_FALSE(received_did_truncate_);
    EXPECT_EQ(0, received_did_write_count_);
}

TEST_F(FileWriterTest, MultiFileWrite)
{
    // Call the webkit facing api.
    const std::string kBlobId("1234");
    writer()->write(kMultiFileWrite_Offset,
        blink::WebString::fromUTF8(kBlobId));

    // Check that the derived class gets called correctly.
    EXPECT_TRUE(testable_writer_->received_write_);
    EXPECT_EQ(testable_writer_->received_write_path_,
        mock_path_as_gurl());
    EXPECT_EQ(kMultiFileWrite_Offset,
        testable_writer_->received_write_offset_);
    EXPECT_EQ(kBlobId, testable_writer_->received_write_blob_uuid_);
    EXPECT_FALSE(testable_writer_->received_truncate_);
    EXPECT_FALSE(testable_writer_->received_cancel_);

    // Check that the client gets called correctly.
    EXPECT_EQ(3, received_did_write_count_);
    EXPECT_TRUE(received_did_write_complete_);
    EXPECT_EQ(3, received_did_write_bytes_total_);
    EXPECT_FALSE(received_did_truncate_);
    EXPECT_FALSE(received_did_fail_);
}

TEST_F(FileWriterTest, CancelFileWriteBeforeCompletion)
{
    // Call the webkit facing api.
    const std::string kBlobId("1234");
    writer()->write(kCancelFileWriteBeforeCompletion_Offset,
        blink::WebString::fromUTF8(kBlobId));

    // Check that the derived class gets called correctly.
    EXPECT_TRUE(testable_writer_->received_write_);
    EXPECT_EQ(testable_writer_->received_write_path_,
        mock_path_as_gurl());
    EXPECT_EQ(kCancelFileWriteBeforeCompletion_Offset,
        testable_writer_->received_write_offset_);
    EXPECT_EQ(kBlobId, testable_writer_->received_write_blob_uuid_);
    EXPECT_TRUE(testable_writer_->received_cancel_);
    EXPECT_FALSE(testable_writer_->received_truncate_);

    // Check that the client gets called correctly.
    EXPECT_TRUE(received_did_fail_);
    EXPECT_EQ(blink::WebFileErrorAbort, fail_error_received_);
    EXPECT_EQ(1, received_did_write_count_);
    EXPECT_FALSE(received_did_write_complete_);
    EXPECT_EQ(1, received_did_write_bytes_total_);
    EXPECT_FALSE(received_did_truncate_);
}

TEST_F(FileWriterTest, CancelFileWriteAfterCompletion)
{
    // Call the webkit facing api.
    const std::string kBlobId("1234");
    writer()->write(kCancelFileWriteAfterCompletion_Offset,
        blink::WebString::fromUTF8(kBlobId));

    // Check that the derived class gets called correctly.
    EXPECT_TRUE(testable_writer_->received_write_);
    EXPECT_EQ(testable_writer_->received_write_path_,
        mock_path_as_gurl());
    EXPECT_EQ(kCancelFileWriteAfterCompletion_Offset,
        testable_writer_->received_write_offset_);
    EXPECT_EQ(kBlobId, testable_writer_->received_write_blob_uuid_);
    EXPECT_TRUE(testable_writer_->received_cancel_);
    EXPECT_FALSE(testable_writer_->received_truncate_);

    // Check that the client gets called correctly.
    EXPECT_TRUE(received_did_fail_);
    EXPECT_EQ(blink::WebFileErrorAbort, fail_error_received_);
    EXPECT_EQ(1, received_did_write_count_);
    EXPECT_FALSE(received_did_write_complete_);
    EXPECT_EQ(1, received_did_write_bytes_total_);
    EXPECT_FALSE(received_did_truncate_);
}

TEST_F(FileWriterTest, CancelFileTruncate)
{
    // Call the webkit facing api.
    writer()->truncate(kCancelFileTruncate_Offset);

    // Check that the derived class gets called correctly.
    EXPECT_TRUE(testable_writer_->received_truncate_);
    EXPECT_EQ(mock_path_as_gurl(),
        testable_writer_->received_truncate_path_);
    EXPECT_EQ(kCancelFileTruncate_Offset,
        testable_writer_->received_truncate_offset_);
    EXPECT_TRUE(testable_writer_->received_cancel_);
    EXPECT_FALSE(testable_writer_->received_write_);

    // Check that the client gets called correctly.
    EXPECT_TRUE(received_did_fail_);
    EXPECT_EQ(blink::WebFileErrorAbort, fail_error_received_);
    EXPECT_FALSE(received_did_truncate_);
    EXPECT_EQ(0, received_did_write_count_);
}

TEST_F(FileWriterTest, CancelFailedTruncate)
{
    // Call the webkit facing api.
    writer()->truncate(kCancelFailedTruncate_Offset);

    // Check that the derived class gets called correctly.
    EXPECT_TRUE(testable_writer_->received_truncate_);
    EXPECT_EQ(mock_path_as_gurl(),
        testable_writer_->received_truncate_path_);
    EXPECT_EQ(kCancelFailedTruncate_Offset,
        testable_writer_->received_truncate_offset_);
    EXPECT_TRUE(testable_writer_->received_cancel_);
    EXPECT_FALSE(testable_writer_->received_write_);

    // Check that the client gets called correctly.
    EXPECT_TRUE(received_did_fail_);
    EXPECT_EQ(blink::WebFileErrorAbort, fail_error_received_);
    EXPECT_FALSE(received_did_truncate_);
    EXPECT_EQ(0, received_did_write_count_);
}

TEST_F(FileWriterTest, DeleteInCompletionCallbacks)
{
    const std::string kBlobId("1234");
    delete_in_client_callback_ = true;
    writer()->write(kBasicFileWrite_Offset,
        blink::WebString::fromUTF8(kBlobId));
    EXPECT_FALSE(testable_writer_.get());

    reset();
    delete_in_client_callback_ = true;
    writer()->truncate(kBasicFileTruncate_Offset);
    EXPECT_FALSE(testable_writer_.get());

    reset();
    delete_in_client_callback_ = true;
    writer()->write(kErrorFileWrite_Offset,
        blink::WebString::fromUTF8(kBlobId));
    EXPECT_FALSE(testable_writer_.get());

    reset();
    delete_in_client_callback_ = true;
    writer()->truncate(kErrorFileTruncate_Offset);
    EXPECT_FALSE(testable_writer_.get());

    // Not crashing counts as passing.
}

} // namespace content
